6.1 Lineare Regression mit Scikit-Learn#
Lernziele#
Lernziele
Sie kennen das lineare Regressionsmodell.
Sie können erste grundlegende Schritte der Datenvorverarbeitung anwenden:
Sie können unvollständige Daten mit dropna aus dem Datensatz entfernen.
Sie können Ausreißer mit drop entfernen.
Sie können eine Eigenschaft als Input auswählen und mit reshape in Matrixform bringen.
Sie können ein lineares Regressionsmodell aus Scikit-Learn laden und mit fit trainieren.
Sie können mit dem trainierten Modell und predict eine Prognose abgeben.
Regression kommt aus der Statistik#
In der Statistik beschäftigen sich Mathematikerinnen und Mathematiker bereits seit Jahrhunderten damit, Analyseverfahren zu entwickeln, mit denen experimentelle Daten gut erklärt werden können. Falls wir eine “erklärende” Variable haben und wir versuchen, die Abhängigkeit einer Messgröße von der erklärenden Variable zu beschreiben, nennen wir das Regressionsanalyse oder kurz Regression. Bei vielen Problemen suchen wir nach einem linearen Zusammenhang und sprechen daher von linearer Regression. Mehr Details finden Sie auch bei Wikipedia → Regressionsanalyse.
Etwas präziser formuliert ist lineare Regression ein Verfahren, bei dem es eine Einflussgröße \(x\) und eine Zielgröße \(y\) mit \(M\) Paaren von dazugehörigen Messwerten \((x^{(1)},y^{(1)})\), \((x^{(2)},y^{(2)})\), \(\ldots\), \((x^{(M)},y^{(M)})\) gibt. Dann sollen zwei Koeffizienten \(\omega_0\) und \(\omega_1\) geschätzt werden, so dass möglichst für alle Datenpunkte \((x^{(i)}, y^{(i)})\) die lineare Gleichung \(y^{(i)} = \omega_0 + \omega_1 x^{(i)}\) gilt. Geometrisch ausgedrückt: durch die Daten soll eine Gerade gelegt werden. Da bei den Messungen auch Messfehler auftreten, werden wir die Gerade nicht perfekt treffen, sondern kleine Fehler machen, die wir hier mit \(\varepsilon^{(i)}\) bezeichnen. Wir suchen also die beiden Parameter \(\omega_0\) und \(\omega_1\), so dass
Die folgende Grafik veranschaulicht das lineare Regressionsmodell. Die Paare von Daten sind in blau gezeichnet, das lineare Regressionsmodell in rot.
Fig. 6 Lineare Regression: die erklärende Variable (= Input oder unabhängige Variable oder Ursache) ist auf der x-Achse, die abhängige Variable (= Output oder Wirkung) ist auf der y-Achse aufgetragen, Paare von Messungen sind in blau gekennzeichnet, das Modell in rot.#
(Quelle: “Example of simple linear regression, which has one independent variable” von Sewaqu. Lizenz: Public domain))
Zu einer Regressionsanalyse gehört mehr als nur die Regressionskoeffizienten zu bestimmen. Daten müssen vorverarbeitet werden, unter mehreren unabhängigen Variablen (Inputs) müssen diejenigen ausgewählt werden, die tatsächlich die Wirkung erklären. Das lineare Regressionsmodell muss trainiert werden und es muss getestet werden. Bei den meisten ML-Modellen gibt es noch Modellparameter, die feinjustiert werden können und die Prognosefähigkeit verbessern.
Im Folgenden erkunden wir einen realistischen Datensatz und bereiten die Daten für das Training eines linearen Regressionsmodells mit Scikit-Learn vor.
Deutscher Gebrauchtwagenmarkt (Autoscout24)#
Um realistische Beispiele zu haben, mit denen wir die lineare Regression erkunden können, laden wir einen Datensatz mit Daten über den deutschen Gebrauchtwagenmarkt von 2011 bis 2021 (Autoscout24). Der Datensatz stammt von Kaggle. Enthalten sind Daten zu
mileage: kilometres traveled by the vehicle (= Kilometerstand)
make: make of the car (= Marke)
model: model of the car (= Modell)
fuel: fuel type (= Treibstoffart)
gear: manual or automatic (= Getriebe)
offerType: type of offer (new, used, …) (= Angebotsart)
price: sale price of the vehicle (= Gebrauchtpreis)
hp: horse power (= PS)
year: the vehicle registration year (= Baujahr)
Wie immer laden wir die Daten und verschaffen uns zunächst einen Überblick.
import pandas as pd
data_raw = pd.read_csv('data/autoscout24-germany-dataset.csv')
data_raw.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 46405 entries, 0 to 46404
Data columns (total 9 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 mileage 46405 non-null int64
1 make 46405 non-null object
2 model 46262 non-null object
3 fuel 46405 non-null object
4 gear 46223 non-null object
5 offerType 46405 non-null object
6 price 46405 non-null int64
7 hp 46376 non-null float64
8 year 46405 non-null int64
dtypes: float64(1), int64(3), object(5)
memory usage: 3.2+ MB
Offensichtlich sind in den Spalten ‘model’, ‘gear’ und ‘hp’ einige Datensätze nicht vollständig. Das erkennen wir daran, dass insgesmt 46.405 Einträge vorliegen, aber in diesen drei Spalten weniger erfasst sind.
Wir machen es uns jetzt einfach und entfernen die nicht vollständigen Daten aus
unserem Datensatz mit der Methode .dropna(), siehe Scikit-Learn
(Dokumentation
dropna).
Bei einem echten Industrieprojekt müssten wir dem Problem nachgehen und die
fehlenden Daten beschaffen. Sollte das nicht gehen, so müssten wir als nächstes
analysieren, warum die Daten fehlen, ob beispielsweise eine Systematik
dahintersteckt, und uns dann einen geeigneten Plan machen, wie mit den fehlenden
Daten umzugehen ist. Das ist ein eigenständiges Thema innerhalb des ML, auf das
wir im nächsten Kapitel noch näher eingehen werden.
data = data_raw.dropna().copy()
data.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 46071 entries, 0 to 46404
Data columns (total 9 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 mileage 46071 non-null int64
1 make 46071 non-null object
2 model 46071 non-null object
3 fuel 46071 non-null object
4 gear 46071 non-null object
5 offerType 46071 non-null object
6 price 46071 non-null int64
7 hp 46071 non-null float64
8 year 46071 non-null int64
dtypes: float64(1), int64(3), object(5)
memory usage: 3.5+ MB
In dem Datensatz gibt es nur vier Eigenschaften, die numerisch sind, also in Form von Zahlen repräsentiert werden. Wir wählen die Eigenschaft Preis als Zielgröße (=abhängige Variable oder Wirkung oder Output).
Als nächstes erkunden wir, wie der Preis abhängig von den Inputs
Kilometerstand,
Baujahr und
PS
ist, indem wir die Daten plotten. Wir fangen mit dem Kilometerstand der Autos an.
import plotly.express as px
fig = px.scatter(data, x = 'mileage', y = 'price', title='Gebrauchtwagenmarkt 2011-2021 (Autoscout24)')
fig.update_layout(
xaxis_title = 'Kilometerstand [km]',
yaxis_title = 'Preis (Euro)'
)
fig.show()
Zunächst einmal stören die Ausreißer etwas. Wir suchen zuerst mal nach den Einträgen mit Preisen über eine halbe Mio. Euro.
filter = data.loc[:, 'price'] > 500000
data.loc[filter, :].head()
| mileage | make | model | fuel | gear | offerType | price | hp | year | |
|---|---|---|---|---|---|---|---|---|---|
| 11753 | 90 | Maybach | Pullman | Gasoline | Automatic | Used | 717078 | 630.0 | 2019 |
| 11754 | 90 | Mercedes-Benz | S 650 | Gasoline | Automatic | Used | 717078 | 630.0 | 2019 |
| 21675 | 431 | Ferrari | F12 | Gasoline | Automatic | Used | 1199900 | 775.0 | 2017 |
Wahrscheinlich handelt es sich bei Eintrag 11753 und 11754 ohnehin um das
gleiche Fahrzeug. Wir entfernen die drei Einträge mit der drop()-Methode,
siehe Pandas Dokumentation
(drop).
data = data.drop([11753, 11754, 21675])
fig = px.scatter(data, x = 'mileage', y = 'price', title='Gebrauchtwagenmarkt 2011-2021 (Autoscout24)')
fig.update_layout(
xaxis_title = 'Kilometerstand [km]',
yaxis_title = 'Preis (Euro)'
)
fig.show()
Sieht nicht besonders linear aus, eher wie eine Hyperbel. Als nächstes betrachten wir den Preis in Abhängigkeit des Baujahrs.
fig = px.scatter(data, x = 'year', y = 'price', title='Gebrauchtwagenmarkt 2011-2021 (Autoscout24)')
fig.update_layout(
xaxis_title = 'Baujahr',
yaxis_title = 'Preis (Euro)'
)
fig.show()
Je jünger, desto teurer, könnte linear sein. Und zuletzt visualisieren wir den Preis abhängig von der PS-Zahl.
fig = px.scatter(data, x = 'hp', y = 'price', title='Gebrauchtwagenmarkt 2011-2021 (Autoscout24)')
fig.update_layout(
xaxis_title = 'Leistung (PS)',
yaxis_title = 'Preis (Euro)'
)
fig.show()
Bei dem Input PS scheint es eine lineare Abhängigkeit zu geben. Je mehr PS desto teurer.
Scikit-Learn: LinearRegression#
Die Bestimmung der Koeffizienten \(\omega_0\) und \(\omega_1\) der Geraden \(y^{(i)} = \omega_0 + \omega x^{(i)}\) funktioniert nicht mehr händisch. Natürlich wäre es möglich, solange Steigungen \(\omega_1\) und y-Achsenabschnitte \(\omega_0\) zu raten, bis eine Gerade herauskommt, die annähernd passt. Scikit-Learn liefert uns sogar die beste Gerade, wobei wir noch diskutieren müssen, was “die beste” genau heißt.
Um Scikit-Learn arbeiten zu lassen, importieren wir die linearen
Regressionsmodelle LinearRegression aus dem Modul sklearn.linear_model.
Danach initialiseren wir das Modell und speichern es in der Variable model.
from sklearn.linear_model import LinearRegression
model = LinearRegression()
/opt/homebrew/Caskroom/miniconda/base/envs/python310/lib/python3.10/site-packages/scipy/__init__.py:146: UserWarning:
A NumPy version >=1.16.5 and <1.23.0 is required for this version of SciPy (detected version 1.25.0
Mit der Methode .fit() werden die Koeffizienten des Modells an die Daten
angepasst. Dazu müssen die Daten in einem bestimmten Format vorliegen. Bei den
Inputs wird davon ausgegangen, dass mehrere Eigenschaften in das Modell eingehen
sollen. Die Eigenschaften stehen normalerweise in den Spalten des Datensatzes.
Beim Output erwarten wir zunächst nur eine Eigenschaft, die durch das Modell
erklärt werden soll. Daher geht Scikit-Learn davon aus, dass der Input eine
Tabelle(Matrix) \(X\) ist, die M Zeilen und N Spalten hat. M ist die Anzahl an
Daten, hier also die Anzahl der Autos, und N ist die Anzahl der Eigenschaften,
die betrachtet werden sollen. Da wir momentan nur die Abhängigkeit des Preises
von der PS-Zahl analysieren wollen, ist \(N=1\). Beim Output geht Scikit-Learn
davon aus, dass eine Datenreihe (eindimensionaler Spaltenvektor) vorliegt, die
natürlich ebenfalls M Zeilen hat. Wir müssen daher unsere PS-Zahlen noch in das
Matrix-Format bringen. Dazu verwenden wir den Trick, dass mit [ [list] ] eine
Tabelle extrahiert wird.
X = data[['hp']]
y = data['price']
# Training des linearen Regressionsmodells
model.fit(X, y);
Nachdem das lineare Regressionsmodell trainiert wurde, können wir die Steigung
in dem Attribut .coef_ablesen und den y-Achsenabschnitt in dem Attribut
.intercept_.
print('Steigung: ')
print(model.coef_)
print('y-Achsenabschnitt: ')
print(model.intercept_)
Steigung:
[186.80491242]
y-Achsenabschnitt:
-8330.026523879089
Damit könnten wir eine Geradengleichung aufstellen und eine Funktion
implementieren, um für eine PS-Zahl eine Prognose abzugeben, welchen
Verkauspreis das Auto erzielen könnte. Aber tatsächlich hat das Scikit-Learn für
uns schon erledigt. Die Methode .predict() berechnet mit den intern
gespeicherten Koeffizienten des linearen Regressionsmodells eine Prognose. Für
eine PS-Zahl von 80 Ps wird ein Verkaufspreis von
model.predict([[80]])
/opt/homebrew/Caskroom/miniconda/base/envs/python310/lib/python3.10/site-packages/sklearn/base.py:450: UserWarning:
X does not have valid feature names, but LinearRegression was fitted with feature names
array([6614.36646953])
6.614 EUR erzielt. Denken Sie daran, dass eine Matrix als Input übergeben werden muss.
Damit können wir auch eine Wertetabelle für PS-Zahlen von 0 bis 800 PS
aufstellen und das Ergebnis zusammen mit den tatsächlichen Verkaufspreisen
visualisieren. Zuerst erzeugen wir die X-Werte, für die das trainierte Modell
eine Prognose aufstellen soll. Wir hätten gerne 100 X-Werte von 0 bis 800 PS.
Dazu nutzen wir aus dem NumPy-Modul den Befehl linspace(start, stopp, anzahl_punkte).
import numpy as np
print(np.linspace(0, 800, 100))
[ 0. 8.08080808 16.16161616 24.24242424 32.32323232
40.4040404 48.48484848 56.56565657 64.64646465 72.72727273
80.80808081 88.88888889 96.96969697 105.05050505 113.13131313
121.21212121 129.29292929 137.37373737 145.45454545 153.53535354
161.61616162 169.6969697 177.77777778 185.85858586 193.93939394
202.02020202 210.1010101 218.18181818 226.26262626 234.34343434
242.42424242 250.50505051 258.58585859 266.66666667 274.74747475
282.82828283 290.90909091 298.98989899 307.07070707 315.15151515
323.23232323 331.31313131 339.39393939 347.47474747 355.55555556
363.63636364 371.71717172 379.7979798 387.87878788 395.95959596
404.04040404 412.12121212 420.2020202 428.28282828 436.36363636
444.44444444 452.52525253 460.60606061 468.68686869 476.76767677
484.84848485 492.92929293 501.01010101 509.09090909 517.17171717
525.25252525 533.33333333 541.41414141 549.49494949 557.57575758
565.65656566 573.73737374 581.81818182 589.8989899 597.97979798
606.06060606 614.14141414 622.22222222 630.3030303 638.38383838
646.46464646 654.54545455 662.62626263 670.70707071 678.78787879
686.86868687 694.94949495 703.03030303 711.11111111 719.19191919
727.27272727 735.35353535 743.43434343 751.51515152 759.5959596
767.67676768 775.75757576 783.83838384 791.91919192 800. ]
Das trainierte Modell erwartet Daten in demselben Format, mit dem es trainiert wurde. Daher erstellen wir nun mit dem NumPy-Array einen Pandas-DataFrame und lassen dann das trainierte Modell die Preise prognostizieren.
X_predict = pd.DataFrame(np.linspace(0, 800, 100), columns=['hp'])
y_predict = model.predict(X_predict)
import plotly.graph_objects as go
fig = go.Figure()
fig.add_trace(go.Scatter(x = data['hp'], y = data['price'], mode='markers', name='Daten'))
fig.add_trace(go.Scatter(x = X_predict['hp'], y = y_predict, mode='lines', name='Prognose'))
fig.update_layout(
title='Gebrauchtwagenmarkt 2011-2021 (Autoscout24)',
xaxis_title = 'Leistung (PS)',
yaxis_title = 'Preis (Euro)'
)
fig.show()
Zusammenfassung#
In diesem Abschnitt haben Sie das theoretische Modell der linearen Regression kennengelernt. Um mit einem realistischen Datensatz zu arbeiten, haben wir einen Datensatz von Kaggle importiert und die Daten vorverarbeitet. Danach haben wir mit Scikit-Learn ein lineare Regressionsmodell trainiert und damit eine Prognose erstellt.